<!-- NativeTooltip.vue - v4：移除默认样式与箭头，仅负责定位与生命周期 -->
<template>
  <!-- 触发器：包裹任意可交互元素 -->
  <span ref="triggerRef" @mouseenter="open" @mouseleave="scheduleClose" @focus="open" @blur="scheduleClose">
    <slot />
  </span>

  <!-- 气泡体：完全交给调用方自定义样式 -->
  <teleport to="body">
    <div v-if="visible" :id="tooltipId" ref="tooltipRef" :style="positionStyle" role="tooltip" @mouseenter="cancelClose" @mouseleave="scheduleClose">
      <slot name="content">{{ content }}</slot>
    </div>
  </teleport>
</template>

<script setup lang="ts">
import { ref, reactive, nextTick, onMounted, onBeforeUnmount, watch } from "vue";

const props = defineProps({
  content: { type: String, default: "" },
  placement: { type: String, default: "top" },
  offset: { type: Number, default: 8 },
  debounce: { type: Number, default: 200 }
});

const triggerRef = ref<HTMLElement | null>(null);
const tooltipRef = ref<HTMLElement | null>(null);
const visible = ref(false);
const positionStyle = reactive<{ top: string; left: string; position: "absolute"; zIndex?: number }>({
  position: "absolute",
  top: "0px",
  left: "0px"
});
let hideTimer: ReturnType<typeof setTimeout> | null = null;
let rafId: number | null = null;
const tooltipId = `tooltip-${Math.random().toString(36).slice(2)}`;

/** 打开 / 关闭 */
function open() {
  clearClose();
  visible.value = true;
  nextTick(scheduleCompute);
}
function scheduleClose() {
  hideTimer = setTimeout(closeNow, props.debounce);
}
function cancelClose() {
  clearClose();
}
function clearClose() {
  if (hideTimer) {
    clearTimeout(hideTimer);
    hideTimer = null;
  }
}
function closeNow() {
  clearClose();
  visible.value = false;
}

/** 位置计算 (RAF 节流 + 视口检测) */
function scheduleCompute() {
  if (rafId != null) return;
  rafId = requestAnimationFrame(() => {
    rafId = null;
    if (!triggerRef.value) return;
    // 触发元素离屏则关闭
    if (!rectInViewport(triggerRef.value.getBoundingClientRect())) {
      closeNow();
      return;
    }
    computePosition();
  });
}

function computePosition() {
  if (!triggerRef.value || !tooltipRef.value) return;
  const tr = triggerRef.value.getBoundingClientRect();
  const rr = tooltipRef.value.getBoundingClientRect();
  const sx = window.scrollX || window.pageXOffset;
  const sy = window.scrollY || window.pageYOffset;
  const order = Array.from(new Set([props.placement, "top", "bottom", "left", "right"]));
  let top = 0,
    left = 0;
  for (const p of order) {
    ({ top, left } = calc(p, tr, rr, sx, sy));
    if (rectFullyInViewport(top, left, rr)) break;
  }
  positionStyle.top = `${top}px`;
  positionStyle.left = `${left}px`;
}

function calc(p: string, tr: DOMRect, rr: DOMRect, sx: number, sy: number) {
  const o = props.offset;
  switch (p) {
    case "bottom":
      return { top: tr.bottom + o + sy, left: tr.left + tr.width / 2 - rr.width / 2 + sx };
    case "left":
      return { top: tr.top + tr.height / 2 - rr.height / 2 + sy, left: tr.left - rr.width - o + sx };
    case "right":
      return { top: tr.top + tr.height / 2 - rr.height / 2 + sy, left: tr.right + o + sx };
    default:
      return { top: tr.top - rr.height - o + sy, left: tr.left + tr.width / 2 - rr.width / 2 + sx };
  }
}
function rectFullyInViewport(top: number, left: number, rect: DOMRect) {
  const vw = window.innerWidth,
    vh = window.innerHeight;
  return left >= 0 && top >= 0 && left + rect.width <= vw && top + rect.height <= vh;
}
function rectInViewport(r: DOMRect) {
  return r.bottom > 0 && r.top < window.innerHeight && r.right > 0 && r.left < window.innerWidth;
}

/** 生命周期 */
onMounted(() => {
  triggerRef.value?.setAttribute("aria-describedby", tooltipId);
  window.addEventListener("scroll", scheduleCompute, { capture: true, passive: true });
  window.addEventListener("resize", scheduleCompute);
});

onBeforeUnmount(() => {
  window.removeEventListener("scroll", scheduleCompute, true);
  window.removeEventListener("resize", scheduleCompute);
  if (rafId != null) cancelAnimationFrame(rafId);
});

watch(visible, v => v && scheduleCompute());
</script>

<style scoped>
/* 留空：完全交由使用方决定样式 */
</style>
